package src;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;

public class Main {
	private static CommUtil com;
	private static Dictionary dict;
	Log log;
	int time;
	private int node;
	TimeTable timeTable;
	private static Nodelist nodelist;
	
	public Main() {
		// Handles terminal input in seperate thread
		Runnable r1 = new Runnable() {
			
			Scanner terminalInput = new Scanner(System.in);
			public void run() {
				while(true) {
					String inputText = terminalInput.nextLine();

					if(inputText.toLowerCase().startsWith("send")) {
						int node = Integer.parseInt(inputText.substring(5));
						System.out.println("Node: " + node);
						try {
							com.send(log.toString(), node, timeTable);
				
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
					
					else if(inputText.toLowerCase().startsWith("insert")) {
						int key = Integer.parseInt(inputText.substring(inputText.indexOf(" ")+1, inputText.lastIndexOf(" ")));
						int value = Integer.parseInt(inputText.substring(inputText.lastIndexOf(" ")+1));
						insert(key, value, false);
						System.out.println("Key: " +  key + " Value: " + value + " inserted in the Dictionary");
					}
					
					else if(inputText.toLowerCase().startsWith("delete")) {
						int key =  Integer.parseInt(inputText.substring(inputText.indexOf(" ")+1));
						System.out.println("Key: " + key);
						if(delete(key, false)) { 
							System.out.println("Key :" +  key + " deleted from the Dictionary");
						} 
					}
					
					else if(inputText.toLowerCase().startsWith("list")) {
						HashMap dic = dict.list();
						Iterator it = dic.entrySet().iterator();
						System.out.println("::: Dictionary :::");
						while(it.hasNext()) {
							System.out.println(it.next());
						}
						System.out.println("::::::::::::::::::");
					}
					
					else if(inputText.toLowerCase().startsWith("connect")) {
						try {
							com.connectToNodes();
						} catch (UnknownHostException e) {
							
							e.printStackTrace();
						} catch (Exception e) {
							
							e.printStackTrace();
						}
					}
					
					else if(inputText.toLowerCase().startsWith("exit")) {
						try {
							log.closeLog();
							com.closeSockets();
							System.exit(0);
							
						} catch (IOException e) {
							//e.printStackTrace();
						}
					}
				}
				
			}
		};
		
		Thread terminalThread = new Thread(r1);
		terminalThread.start();
	}
	


//	public int getId() {
//		return this.node;
//	}
	
	public void setOwnId() {
		// Reads the config file to find own id
		boolean found = false;
		try{
		    FileInputStream fstream = new FileInputStream("config.txt");
		    DataInputStream in = new DataInputStream(fstream);
		    BufferedReader br = new BufferedReader(new InputStreamReader(in));
		    String strLine;
		    while ((strLine = br.readLine()) != null)   {
		    	String hostname = strLine.substring(strLine.indexOf(" ")+1);
		    	if(InetAddress.getByName(hostname).equals((InetAddress.getLocalHost()))) {
		    		this.node = Integer.parseInt(strLine.substring(0, strLine.indexOf(" ")));
		    		found = true;
		    		
		    	}
		    	
		    }

		    in.close();
	    }catch (Exception e){
	      System.err.println("Error: " + e.getMessage());
	    }
	    
	    System.out.println("[Setup]: Node id set to: " + node );
	}
	
	public void insert(int key, int value, boolean fromlog) {
		int id = getNodeID();
		dict.insert(key, value);
		if(!fromlog) {
			time++;
			timeTable.setClock(id, id, getTime());
			Event e = new Event("insert",timeTable.getClock(id,id),id, key, value);
			log.addEvent(e);
			System.out.println("not from log, should print");
			log.writeToFile(e.toString());
		} else {
			timeTable.setClock(id, id, getTime());
			Event e = new Event("insert",timeTable.getClock(id,id),id, key, value);
			log.addEvent(e);
		}
	}
	
	public boolean delete(int key, boolean fromlog) {
		int id = getNodeID();
		if (dict.list().containsKey(key)) {
			dict.delete(key);
			if(!fromlog) {
				time++;
				timeTable.setClock(id, id, getTime());
				Event e = new Event("delete",timeTable.getClock(id, id),id, key);
				log.addEvent(e);
				System.out.println("not from log, should print");
				log.writeToFile(e.toString());
			} else {
				timeTable.setClock(id, id, getTime());
				Event e = new Event("delete",timeTable.getClock(id, id),id, key);
				log.addEvent(e);
			}
			return true;
		}
		return false;
	}
	
	public int getTime() {
		return this.time;
	}
	
	public int getNodeID() {
		return this.node;
	}
	
	public boolean differentTimeTables(TimeTable otherTable) {
		boolean diff = false;
		for (int i=0; i<5; i++) {
			for (int j=0; j<5; j++) {
				if (timeTable.getClock(i,j)<otherTable.getClock(i,j)) {
					diff = true;
					//timeTable.setClock(i, j, otherTable.getClock(i,j));
				}
			}
		}
		return diff;
	}
	
	public void updateTimeTable(TimeTable otherTable) {
		for (int i=0; i<5; i++) {
			for (int j=0; j<5; j++) {
				if (timeTable.getClock(i,j)<otherTable.getClock(i,j)) {
					timeTable.setClock(i, j, otherTable.getClock(i,j));
				}
			}
		}
	}
	
	
	
	
	public void parseMsg(String log, boolean fromlog) {
		String[] newLog = log.split("Timetable:");
		String otherTable = newLog[1];
		
		//check if timetables are different
		if (differentTimeTables(otherTable)) {
			// do necessary log updates
			//update();
		
			if(!newLog[0].equals("")) {
				ArrayList<Event> newEvents = new ArrayList<Event>();
				String op = "";
				int time = 0;
				int node = 0;
				int key = 0;
				int value = 0;
				String[] events = log.split(":");
				for (int i=0; i<events.length; i++) {
					String[] fields = events[i].split(",");
					op = fields[0];
					time = Integer.parseInt(fields[1]);
					node = Integer.parseInt(fields[2]);
					key = Integer.parseInt(fields[3]);
	
					if (fields.length>4) {
						value = Integer.parseInt(fields[4]);
						Event newEvent = new Event(op,time,node,key,value);
						newEvents.add(newEvent);
					} else {
						Event newEvent = new Event(op,time,node,key);
						newEvents.add(newEvent);
			
					}	
				}
				executeEvents(newEvents, fromlog);
			}
		}


		
	}
	
	public void executeEvents(ArrayList<Event> events, boolean fromlog) {
		for (int i=0; i<events.size(); i++) {
			Event e = events.get(i);
			if (e.getOperation().equals("insert")) {
				insert(e.getKey(), e.getValue(), fromlog);
			}
			if (e.getOperation().equals("delete")) {
				delete(e.getKey(), fromlog);
			}
		}	
	}
	
	public static void main(String[] args) throws IOException {
		Main main = new Main();
		main.setOwnId();
		main.time = 0;
		main.timeTable = new TimeTable();
		main.nodelist = new Nodelist();
		main.com = new CommUtil(main);
		main.dict = new Dictionary();
		main.log = new Log(main.node);
		String logstring = main.log.readLog();
		main.parseMsg(logstring, true);
		Thread comThread = new Thread(com);
		comThread.start();

	}

}
